﻿using System;
using System.Collections.Generic;
using System.Drawing;
using GraphDrawing2.Canvas;
using GraphDrawing2.Core;
using GraphDrawing2.V2.Concrete.Model;

namespace GraphDrawing2.V2.Concrete.Renderers
{
    public class BackgroundRenderingPresenter : IPainter
    {
        readonly BackgroundModel model;
        readonly IBackgroundRenderer view;

        BackgroundRenderingPresenter(ViewPortModel viewPort, IRendererFactory factory, bool showGrid, int gridSize)
        {
            model = new BackgroundModel(viewPort);
            view = factory.CreateBackgroundRenderer();
            view.Initialize(model);
            model.GridSize = gridSize;
            model.ShowGrid = showGrid;
        }

        public BackgroundRenderingPresenter(ViewPortModel viewPort, IRendererFactory factory, int gridSize) : this(viewPort, factory, true, gridSize)
        {
        }

        public BackgroundRenderingPresenter(ViewPortModel viewPort, IRendererFactory factory) : this(viewPort, factory,false, 0)
        {
        }

        public void Draw(DrawingContext dc)
        {
            view.Draw(dc);
        }
    }

    public class BackgroundModel
    {
        readonly ViewPortModel viewPort;
        int deltaSize;
        public int GridSize { get; set; }
        public int GridCellCount { get; set; }
        public bool UseSmallGrid { get; set; }
        public bool ShowGrid { get; set; }

        public BackgroundModel(ViewPortModel viewPort)
        {
            GridSize = 256;
            UseSmallGrid = true;
            GridCellCount = 4;           
            this.viewPort = viewPort;
        }

        public IEnumerable<LineF> GetGridLines()
        {
            deltaSize = UseSmallGrid ? GridSize/GridCellCount : GridSize;
            var viewTopLeft = viewPort.ToWorld(new PointF(0, 0));
            var leftVertLine = GetNearestVisibleGridLine(viewTopLeft.X, deltaSize);
            var topHorzLine = GetNearestVisibleGridLine(viewTopLeft.Y, deltaSize);
            var viewBottomRight = viewPort.ToWorld(new PointF(viewPort.Size.Width, viewPort.Size.Height));
            var rightVertLine = GetNearestVisibleGridLine(viewBottomRight.X, deltaSize);
            var bottomHorzLine = GetNearestVisibleGridLine(viewBottomRight.Y, deltaSize);

            for (var x = leftVertLine; x <= rightVertLine; x += deltaSize)
                yield return new LineF(x, viewTopLeft.Y, x, viewBottomRight.Y, (((int) x)%GridSize) == 0);
            for (var y = topHorzLine; y <= bottomHorzLine; y += deltaSize)
                yield return new LineF(viewTopLeft.X, y, viewBottomRight.X, y, (((int) y)%GridSize) == 0);
        }

        /// <summary>
        /// Возвращает координату ближайшей видимой линии сетки.
        /// </summary>
        static float GetNearestVisibleGridLine(float coord, float delta)
        {
            return coord - coord%delta;
        }
    }


    public class BackgroundView : IBackgroundRenderer
    {
        BackgroundModel model;
        public Appearance Background { get; private set; }
        public LineAppearance Grid { get; private set; }
        public LineAppearance Cell { get; private set; }

        public BackgroundView()
        {
            Background = new Appearance {Color = Color.FloralWhite};
            Grid = new LineAppearance {Color = Color.FromArgb(128, Color.Black)};
            Cell = new LineAppearance {Color = Color.FromArgb(128, Color.LightGray)};
        }

        public void Initialize(BackgroundModel model)
        {
            this.model = model;
        }

        public void Draw(DrawingContext dc)
        {
            dc.graphics.Clear(Background.Color);
            if (!model.ShowGrid)
                return;
            using (var gridPen = Grid.CreatePen())
            using (var cellPen = Cell.CreatePen())
            {
                foreach (var line in model.GetGridLines())
                    dc.graphics.DrawLine(line.isGrid ? gridPen : cellPen, line.p1, line.p2);
            }
        }
    }


    public struct LineF
    {
        public readonly PointF p1;
        public readonly PointF p2;
        public readonly bool isGrid;

        public LineF(PointF p1, PointF p2, bool isGrid)
        {
            this.p1 = p1;
            this.p2 = p2;
            this.isGrid = isGrid;
        }

        public LineF(float x1, float y1, float x2, float y2, bool isGrid)
            : this(new PointF(x1, y1), new PointF(x2, y2), isGrid)
        {
        }

        public bool IsVertical
        {
            get { return Math.Abs(p1.X - p2.X) < 0.0001; }
        }

        public bool IsHorizontal
        {
            get { return Math.Abs(p1.Y - p2.Y) < 0.0001; }
        }
    }
}